iT邦幫忙

2021 iThome 鐵人賽

DAY 11
0
自我挑戰組

從C到JS的同步非同步探索系列 第 11

[Day 11] .Net Task 底層(4)

  • 分享至 

  • xImage
  •  

前言

今天要聊到的是 Task 把超過自己承載能力的任務放入 TP 交給別條 thread 執行的過程發生了甚麼 ?

參考函數 : ThreadPool.UnsafeQueueCustomWorkItem

正文

翻看原始碼 ThreadPool.UnsafeQueueCustomWorkItem

[SecurityCritical]
internal static void UnsafeQueueCustomWorkItem(IThreadPoolWorkItem workItem, bool forceGlobal)
{
    Contract.Assert(null != workItem);
    EnsureVMInitialized();

    //
    // Enqueue needs to be protected from ThreadAbort
    //
    try { }
    finally
    {
        ThreadPoolGlobals.workQueue.Enqueue(workItem, forceGlobal);
    }
}

先來看參數

ThreadPool.UnsafeQueueCustomWorkItem(new CompletionActionInvoker(singleTaskCompletionAction, this), forceGlobal: false);

CompletionActionInvoker 也就是原始碼的 IThreadPoolWorkItem workItem在 Task 的第一集就有提到, 創建時會把待完成任務存入, 並且留下介面供別人觸發此任務。

再來看看 ThreadPoolGlobals

internal static class ThreadPoolGlobals
{
    //Per-appDomain quantum (in ms) for which the thread keeps processing
    //requests in the current domain.
    public static uint tpQuantum = 30U;

    public static int processorCount = Environment.ProcessorCount;

    public static bool tpHosted = ThreadPool.IsThreadPoolHosted();

    public static volatile bool vmTpInitialized;
    public static bool enableWorkerTracking;

    [SecurityCritical]
    public static ThreadPoolWorkQueue workQueue = new ThreadPoolWorkQueue();

    [System.Security.SecuritySafeCritical] // static constructors should be safe to call
    static ThreadPoolGlobals()
    {
    }
}

發現這是一個 static 的變數, 其成員變數有一個 workQueue

而之前所說的, 把待完成任務放入 TP 由別條 thread 執行, 原來指的就是把任務 push 進 workQueue 這個資料結構。

此刻, 相信碰過 linux 的讀者會很快了聯想起 workQueue 這個資料結構所代表的意義, 所以我心急如焚的察看Enqueue method。

[SecurityCritical]
public void Enqueue(IThreadPoolWorkItem callback, bool forceGlobal)
{
		// forceGlobal=false ,獲取當前 thread 的任務列表
    ThreadPoolWorkQueueThreadLocals tl = null;
    if (!forceGlobal)
        tl = ThreadPoolWorkQueueThreadLocals.threadLocals;

    if (loggingEnabled)
        System.Diagnostics.Tracing.FrameworkEventSource.Log.ThreadPoolEnqueueWorkObject(callback);
		// 任務列表不為空, 加入新任務
    if (null != tl)
    {
        tl.workStealingQueue.LocalPush(callback);
    }
    else
    {
				// 任務列表為空, 加入新任務
        QueueSegment head = queueHead;

        while (!head.TryEnqueue(callback))
        {
            Interlocked.CompareExchange(ref head.Next, new QueueSegment(), null);

            while (head.Next != null)
            {
                Interlocked.CompareExchange(ref queueHead, head.Next, head);
                head = queueHead;
            }
        }
    }
		// 調用 worker thread 進行 schedule (此段程式碼利用 dll 引入)
    EnsureThreadRequested();
}

除了利用 CAS 方法 lock-free 的把存放進屬於 thread 的私人任務存放區外

果然找到了 , worker Thread 的調用, 以下簡單聊聊我對這段的理解。

小結

我會先說說何謂 linux 中的 workQueue , 因為在這段程式碼最後, 我們發現 .Net 向外引用了 worker thread 而這是外面的程式碼, 我看不到, 所以此刻只能用 workQueue來猜測其功能。接著說說我對 .Net ThreadPool的理解。

workQueue

以 linux 的workQueue為例(注:我程度不足,僅簡單說說)

workQueue創建時, 會創建 thread pool 作為 queue pool , 每個 queue 又可以放入多個任務, 所以呈現以下結構。

workQueue

  1. queue 1 ( thread 1 )
    1. task 1
    2. task 2
    3. task 3
  2. queue 2 ( thread 2 )
    1. NULL
  3. queue 3 ( thread 3 )
    1. task 4
  4. worker thread ( thread 4 )

發現裡面還多了一條 thread 名叫 worker thread , 其功能是排程 thread 1~3 , 包含 :

  1. 閒置沒有任務的 thread (ex thread 2 )
  2. 喚醒有任務的 thread 運行 等等。( ex thread 1,3 )

利用這樣的作法, 增進 multi-thread CPU 使用率, 不要有很多 thread 明明沒事做, 卻還在運作。

ThreadPool

以下從前天 Task 運行完成, 發現連續任務區有任務開始, 按照步驟陳述

  1. Task 進入完成階段
  2. Task 檢查連續任務區
  3. Task 發現連續任務區有任務
  4. Task 想直接接下去用同一條 thread 繼續執行, 但被禁止 ( 若沒禁止, 進入昨天的情況 )
  5. Task 調用 UnsafeQueueCustomWorkItem 把任務放入全域變數 ThreadPoolGlobals中的 workQueue
  6. 任務被放到 workQueue後被按照 thread 掛載到 thread 的任務區中
  7. 外來 method RequestWorkerThread 此時被呼叫, 會創建 workerThread
  8. workerThread 檢查各條 thread 的任務區, 使任務區為空的 thread 閒置, 喚醒任務區有任務的 thread 等等....。

至於被喚醒的 thread 內的任務如何被觸發, 會在之後說明。

到此 .Net thread pool 的探底算是告一段落, 可以發現, 若是用 linux 的 workQueue 的角度來理解, .Net 的 thread pool 就是實踐了上半部分的 workQueue , 包含維護各個 thread 的 queue, 並且把待執行的任務, 依情況放入各個 thread 的 queue 中。但是 .Net 本身不去排程這些 queue 和他們裡面的任務。

.Net 調用外來方法創建一條排程 thread , 這條排程 thread 會完成 workQueue 的排程任務, 包含

  1. 排程 ⇒ 調換各條 thread 的執行順序, 中斷卡住的 thread , 閒置沒有任務的 thread 等等......

注 : 關於外來排程 thread 的功能, 我僅能猜測, 因為原始碼不好找, 其調用了外部程式。有錯可以直接向我說~~

明天進度

透過這幾天的努力, 我們了解了 Task 執行完本身任務後處理連續任務區的任務的方法。

明天我們就開始來看看 Task 是怎麼執行本身的任務吧

明天見 !


上一篇
[Day 10] .Net Task 底層(3)
下一篇
[Day12] .Net Task 底層(5)
系列文
從C到JS的同步非同步探索30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言